<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="wFjyN" id="wFjyN"><span data-lake-id="u8447d5a4" id="u8447d5a4">典型回答</span></h1>
  <p data-lake-id="ue19dd2dc" id="ue19dd2dc"><br></p>
  <p data-lake-id="ueeec0e98" id="ueeec0e98"><span data-lake-id="ucc0a8852" id="ucc0a8852">Apache POI，是一个非常流行的文档处理工具，通常大家会选择用它来处理Excel文件。但是在实际使用的时候，经常会遇到内存溢出的情况，那么，为啥他会导致内存溢出呢？</span></p>
  <p data-lake-id="u16cd57e5" id="u16cd57e5"><span data-lake-id="u35b2814d" id="u35b2814d">​</span><br></p>
  <h3 data-lake-id="eWTZ6" id="eWTZ6"><span data-lake-id="ub796c87f" id="ub796c87f">Excel并没看到的那么小</span></h3>
  <p data-lake-id="u724d3ce1" id="u724d3ce1"><br></p>
  <p data-lake-id="uc3c217a4" id="uc3c217a4"><span data-lake-id="u47eba1e8" id="u47eba1e8">我们通常见到的xlsx文件，其实是一个个压缩文件。它们把若干个XML格式的纯文本文件压缩在一起，Excel就是读取这些压缩文件的信息，最后展现出一个完全图形化的电子表格。</span></p>
  <p data-lake-id="ub5bd8843" id="ub5bd8843"><span data-lake-id="u0d45d53f" id="u0d45d53f">​</span><br></p>
  <p data-lake-id="u2af446cc" id="u2af446cc"><span data-lake-id="u28791091" id="u28791091">所以，如果我们把xlsx文件的后缀更改为.zip或.rar，再进行解压缩，就能提取出构成Excel的核心源码文件。解压后会发现解压后的文件中有3个文件夹和1个XML格式文件：</span></p>
  <p data-lake-id="ub127effb" id="ub127effb"><span data-lake-id="uc54e6ed7" id="uc54e6ed7">​</span><br></p>
  <p data-lake-id="u52eef1af" id="u52eef1af"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1700370311465-879339cb-8cc4-4552-be0b-b2e9e09fc1a6.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_39%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u96f77002" id="u96f77002"><br></p>
  <blockquote data-lake-id="u6d1f0c22" id="u6d1f0c22">
   <p data-lake-id="u86ec2e55" id="u86ec2e55"><span data-lake-id="uc598f1e2" id="uc598f1e2" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">_rels 文件夹 看里面数据像是一些基础的配置信息,比如 workbook 文件的位置等信息,一般不会去动它.</span></p>
   <p data-lake-id="u60072873" id="u60072873"><span data-lake-id="u332ff313" id="u332ff313" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">​</span><br></p>
   <p data-lake-id="udafba8a4" id="udafba8a4"><span data-lake-id="u8c759e7d" id="u8c759e7d" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">docProps 文件夹 docProps 文件夹下重要的文件是一个 app.xml,这里面主要存放了 sheet 的信息,如果想添加或编辑 sheet 需要改这个文件.其他文件都是一些基础信息的数据,比如文件所有者,创建时间等.</span></p>
   <p data-lake-id="u28c0bd4d" id="u28c0bd4d"><span data-lake-id="u80a53eb2" id="u80a53eb2" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">​</span><br></p>
   <p data-lake-id="uef50db49" id="uef50db49"><span data-lake-id="u9564a567" id="u9564a567" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">xl 文件夹 xl 文件夹是最重要的一个文件夹,里面存放了 Sheet 中的数据,行和列的格式,单元格的格式,sheet 的配置信息等等信息.</span></p>
  </blockquote>
  <p data-lake-id="uc3d4f2a4" id="uc3d4f2a4"><span data-lake-id="ue8595f53" id="ue8595f53" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">​</span><br></p>
  <p data-lake-id="udb1f61a5" id="udb1f61a5"><span data-lake-id="ua4976aec" id="ua4976aec" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">所以，实际上我们处理的xlsx文件实际上是一个经过高度压缩的文件格式，背后是有好多文件支持的。所以，我们看到的一个文件可能只有2M，但是实际上这个文件未压缩情况下可能要比这大得多。</span></p>
  <p data-lake-id="u89fe5bff" id="u89fe5bff"><span data-lake-id="u4158ea17" id="u4158ea17" class="lake-fontsize-12" style="color: rgb(64, 64, 64)">​</span><br></p>
  <p data-lake-id="u80717162" id="u80717162"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1700370665743-e19f028b-f4f5-48a6-afa3-4673aadf08dd.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_41%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ufa1110ec" id="ufa1110ec"><br></p>
  <p data-lake-id="u1fccfc05" id="u1fccfc05"><span data-lake-id="u7f26c72f" id="u7f26c72f">也就是说，POI在处理的时候，处理的实际上并不只是我们看到的文件大小，实际上他的大小大好几倍。</span></p>
  <p data-lake-id="u7ce9731c" id="u7ce9731c"><span data-lake-id="u055d5e0b" id="u055d5e0b">​</span><br></p>
  <p data-lake-id="u3b721387" id="u3b721387"><strong><span data-lake-id="u5e68a610" id="u5e68a610">这是为什么明明我们处理的文件只有100多兆，但是实际却可能占用1G内存的其中一个原因。当然这只是其中一个原因，还有一个原因，我们就需要深入到POI的源码中来看了。</span></strong></p>
  <p data-lake-id="u72c19727" id="u72c19727"><strong><span data-lake-id="u82037d4f" id="u82037d4f">​</span></strong><br></p>
  <p data-lake-id="u99f3a131" id="u99f3a131"><strong><span data-lake-id="u69e0af41" id="u69e0af41">​</span></strong><br></p>
  <h3 data-lake-id="vGpZL" id="vGpZL"><span data-lake-id="u536c59d2" id="u536c59d2">POI的溢出原理</span></h3>
  <p data-lake-id="ub4a8ea40" id="ub4a8ea40"><br></p>
  <p data-lake-id="u78d3dbcb" id="u78d3dbcb"><span data-lake-id="u692c8a26" id="u692c8a26">我们拿POI的文件读取来举例，一般来说文件读取出现内存溢出的情况更多一些。以下是一个POI文件导出的代码示例：</span></p>
  <p data-lake-id="ua9e01b76" id="ua9e01b76"><span data-lake-id="ua59c17f2" id="ua59c17f2">​</span><br></p>
  <pre lang="java"><code>
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ExcelReadTest {

    public static void main(String[] args) {
        // 指定要读取的文件路径
        String filename = "example.xlsx";

        try (FileInputStream fileInputStream = new FileInputStream(new File(filename))) {
            // 创建工作簿对象
            Workbook workbook = new XSSFWorkbook(fileInputStream);

            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);

            // 遍历所有行
            for (Row row : sheet) {
                // 遍历所有单元格
                for (Cell cell : row) {
                    Thread.sleep(100);
                    // 根据不同数据类型处理数据
                    switch (cell.getCellType()) {
                        case STRING:
                            System.out.print(cell.getStringCellValue() + "\t");
                            break;
                        case NUMERIC:
                            if (DateUtil.isCellDateFormatted(cell)) {
                                System.out.print(cell.getDateCellValue() + "\t");
                            } else {
                                System.out.print(cell.getNumericCellValue() + "\t");
                            }
                            break;
                        case BOOLEAN:
                            System.out.print(cell.getBooleanCellValue() + "\t");
                            break;
                        case FORMULA:
                            System.out.print(cell.getCellFormula() + "\t");
                            break;
                        default:
                            System.out.print(" ");
                    }
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}

</code></pre>
  <p data-lake-id="u5d278372" id="u5d278372"><br></p>
  <p data-lake-id="uc8e6ec43" id="uc8e6ec43"><span data-lake-id="udc8ea9a1" id="udc8ea9a1">这里面用到了一个关键的XSSFWorkbook类，</span></p>
  <p data-lake-id="u441f6470" id="u441f6470"><span data-lake-id="u15742f83" id="u15742f83">​</span><br></p>
  <pre lang="java"><code>
public XSSFWorkbook(InputStream is) throws IOException {
    this(PackageHelper.open(is));
}

public static OPCPackage open(InputStream is) throws IOException {
    try {
        return OPCPackage.open(is);
    } catch (InvalidFormatException e){
        throw new POIXMLException(e);
    }
}
</code></pre>
  <p data-lake-id="u26f8fa54" id="u26f8fa54"><span data-lake-id="u6864e928" id="u6864e928">​</span><br></p>
  <p data-lake-id="u5f3a9eb2" id="u5f3a9eb2"><span data-lake-id="u78456c0b" id="u78456c0b">最终会调用到OPCPackage.open方法，看看这个方法是咋实现的：</span></p>
  <p data-lake-id="ue7284884" id="ue7284884"><span data-lake-id="ud9296f52" id="ud9296f52">​</span><br></p>
  <pre lang="java"><code>
/**
 * Open a package.
 *
 * Note - uses quite a bit more memory than {@link #open(String)}, which
 * doesn't need to hold the whole zip file in memory, and can take advantage
 * of native methods
 *
 * @param in
 *            The InputStream to read the package from
 * @return A PackageBase object
 *
 * @throws InvalidFormatException
 * 				Throws if the specified file exist and is not valid.
 * @throws IOException If reading the stream fails
 */
public static OPCPackage open(InputStream in) throws InvalidFormatException,
        IOException {
    OPCPackage pack = new ZipPackage(in, PackageAccess.READ_WRITE);
    try {
        if (pack.partList == null) {
            pack.getParts();
        }
    } catch (InvalidFormatException | RuntimeException e) {
        IOUtils.closeQuietly(pack);
        throw e;
    }
    return pack;
}
</code></pre>
  <p data-lake-id="uff1dd1d2" id="uff1dd1d2"><br></p>
  <p data-lake-id="u08c75c00" id="u08c75c00"><span data-lake-id="u93391a0b" id="u93391a0b">这行代码的注释中说了：这个方法会把整个压缩文件都加载到内存中。也就是把整个 Excel 文档加载到内存中，可想而知，这在处理大型文件时是肯定会导致导致内存溢出的。</span></p>
  <p data-lake-id="u9677b6a8" id="u9677b6a8"><span data-lake-id="u4b40a225" id="u4b40a225">​</span><br></p>
  <p data-lake-id="u3a28787b" id="u3a28787b"><span data-lake-id="u19532f48" id="u19532f48">也就是说我们使用的XSSFWorkbook（包括HSSFWorkbook也同理）在处理Excel的过程中会将整个Excel都加载到内存中，在文件比较大的时候就会导致内存溢出。</span></p>
  <p data-lake-id="u3f4fae19" id="u3f4fae19"><span data-lake-id="uce8967dc" id="uce8967dc">​</span><br></p>
  <h1 data-lake-id="mSUt6" id="mSUt6"><span data-lake-id="u6268e769" id="u6268e769">扩展知识</span></h1>
  <h2 data-lake-id="Hv1g0" id="Hv1g0"><span data-lake-id="u8dab7bce" id="u8dab7bce">几种Workbook格式</span></h2>
  <p data-lake-id="u2fba92e4" id="u2fba92e4"><br></p>
  <p data-lake-id="u9956842f" id="u9956842f"><span data-lake-id="u0f44d390" id="u0f44d390">POI中提供了很多种WorkBook API来操作Excel，有的适合大文件读写，有的不适合。</span></p>
  <p data-lake-id="u5931b949" id="u5931b949"><span data-lake-id="u5a28dec9" id="u5a28dec9">​</span><br></p>
  <p data-lake-id="ue31c413d" id="ue31c413d"><strong><span data-lake-id="uda4a8e4b" id="uda4a8e4b" style="color: var(--text-primary)">SSFWorkbook</span></strong></p>
  <ul list="u99402dad">
   <li fid="u0b5f9d98" data-lake-id="u95b68a5a" id="u95b68a5a"><span data-lake-id="u273a9bd6" id="u273a9bd6">用于处理 Excel 的 .xls 格式（即 Excel 97-2003）。</span></li>
  </ul>
  <p data-lake-id="uc6a6db6a" id="uc6a6db6a"><strong><span data-lake-id="u692ef978" id="u692ef978" style="color: var(--text-primary)">XSSFWorkbook</span></strong></p>
  <ul list="u2f61fa4a">
   <li fid="u26966bc7" data-lake-id="u77850b9f" id="u77850b9f"><span data-lake-id="ud49a4a30" id="ud49a4a30" style="color: var(--text-primary)">用于处理 Excel 的 </span><span data-lake-id="u066f3d00" id="u066f3d00" style="color: var(--tw-prose-code)">.xlsx</span><span data-lake-id="u0ac53209" id="u0ac53209" style="color: var(--text-primary)"> 格式（即 Excel 2007 及以后版本的）。支持更大的数据集和更多的功能，如更好的样式和公式支持。但是相对于 </span><span data-lake-id="ud2073952" id="ud2073952" style="color: var(--tw-prose-code)">HSSFWorkbook</span><span data-lake-id="u158408b9" id="u158408b9" style="color: var(--text-primary)">，它在处理大数据集时可能占用更多内存。</span></li>
  </ul>
  <p data-lake-id="uea408e48" id="uea408e48"><strong><span data-lake-id="u77ad7773" id="u77ad7773" style="color: var(--text-primary)">SXSSFWorkbook</span></strong></p>
  <ul list="u59cfd60f">
   <li fid="u8a5cca4c" data-lake-id="ub064545e" id="ub064545e"><span data-lake-id="u846d9dbe" id="u846d9dbe" style="color: var(--text-primary)">用于处理 </span><span data-lake-id="u5dd127dd" id="u5dd127dd" style="color: var(--tw-prose-code)">.xlsx</span><span data-lake-id="u198cead7" id="u198cead7" style="color: var(--text-primary)"> 格式。它是 </span><span data-lake-id="u63b936fc" id="u63b936fc" style="color: var(--tw-prose-code)">XSSFWorkbook</span><span data-lake-id="u13fb34eb" id="u13fb34eb" style="color: var(--text-primary)"> 的流式版本，专门设计用于处理大数据集。通过将数据写入临时文件而非全部保留在内存中，显著减少内存消耗。特别适合用于创建大型数据集的 Excel 文件。</span><span data-lake-id="u57dcda9c" id="u57dcda9c">​</span></li>
  </ul>
  <p data-lake-id="u0d8a8964" id="u0d8a8964"><strong><span data-lake-id="u7d6cb525" id="u7d6cb525" style="color: #F38F39">​</span></strong><br></p>
  <h2 data-lake-id="mZvCb" id="mZvCb"><span data-lake-id="u88a3fce1" id="u88a3fce1">几种Workbook性能对比</span></h2>
  <p data-lake-id="u8bfa9fa1" id="u8bfa9fa1"><br></p>
  <p data-lake-id="u8a657e4e" id="u8a657e4e"><span data-lake-id="u831b6311" id="u831b6311">​</span><br></p>
 </body>
</html>